/**
 * Copyright 2012 Roger Cockshutt
 * krakenbrau@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.parallelrl.data.ecs;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.googlecode.parallelrl.display.Colors;
import com.googlecode.parallelrl.display.GameLogger;
import com.googlecode.parallelrl.process.Game;
import com.googlecode.parallelrl.util.ComponentPair;

/**
 * Backbone of the Entity-Component System.  Inspired by and derived from work by Adam Martin (t-machine.org).
 * 
 * @author Roger Cockshutt
 *
 */
public abstract class ECSManager {
	
	// Map<A, Map<B, C>> ->
	// Column indices are outer map keys (A)
	// Row indices are inner map keys (B)
	// Entries are inner map values (C)
	
	private static HashMap<String, TreeMap<String, String>> assemblages = new HashMap<>();
	
	private static HashMap<String, HashMap<Long, Long>> ecsMap = new HashMap<>();
	private static HashMap<Long, String> componentDataTable = new HashMap<>();
	
	private static Vector<Long> allEntities = new Vector<>();
	private static Long lastEntityId = 0L;
	private static Long lastDataId = 0L;
	private static Long lastEntity = null;
	
	public static void loadAssemblageData(List<String> lines) {
		HashMap<String, Vector<ComponentPair>> entries = parseData(lines);
		for (String name : entries.keySet()) {
			TreeMap<String, String> components = new TreeMap<>();
			for (ComponentPair cmp : entries.get(name)) {
				components.put(cmp.name(), cmp.value());
			}
			assemblages.put(name, components);
		}
	}
	
	public static TreeMap<String, String> getAssemblage(String name) {
		return assemblages.get(name);
	}
	
	public static Integer assemblageCount() {
		return assemblages.size();
	}
	
	public static List<String> getAllAssemblagesWithComponentValue(String component, String val) {
		Vector<String> names = new Vector<>();
		for (String name : assemblages.keySet()) {
			String value = assemblages.get(name).get(component);
			if (value != null && value.equals(val)) {
				names.add(name);
			}
		}
		return names;
	}
	
	public static List<String> getAllAssemblagesWithComponent(String component) {
		Vector<String> names = new Vector<>();
		for (String name : assemblages.keySet()) {
			String value = assemblages.get(name).get(component);
			if (value != null) {
				names.add(name);
			}
		}
		return names;
	}
	
	public static List<String> getAllAssemblages() {
		Vector<String> names = new Vector<>();
		for (String name : assemblages.keySet()) {
			names.add(name);
		}
		return names;
	}
	
	public static void setComponent(Long entity, ComponentPair component) {
		if (!doesEntityExist(entity)) {
			return;
		}
		if (!ecsMap.containsKey(component.name())) {
			ecsMap.put(component.name(), new HashMap<Long, Long>());
		}
		Long dataID = null;
		if (!ecsMap.get(component.name()).containsKey(entity)) {
			dataID = getNextDataId();
		} else {
			dataID = ecsMap.get(component.name()).get(entity);
		}
		componentDataTable.put(dataID, component.value());
		ecsMap.get(component.name()).put(entity, dataID);
	}
	
	public static void setComponent(Long entity, String component, String value) {
		setComponent(entity, new ComponentPair(component, value));
	}
	
	public static void removeComponent(Long entity, String component) {
		HashMap<Long, Long> dataIDMap = ecsMap.get(component);
		if (dataIDMap != null) {
			Long IDref = dataIDMap.get(entity);
			dataIDMap.remove(entity);
			componentDataTable.remove(IDref);
			lastDataId = IDref;
		}
	}
	
	public static Long createEntityFromAssemblage(String name) {
		return createEntityFromAssemblage(name, getNextEntityId());
	}
	
	public static Long createEntityFromAssemblage(String name, Long id) {
		if (doesEntityExist(id)) {
			return null;
		}
		TreeMap<String, String> assemblage = assemblages.get(name);
		if (assemblage == null) {
			return null;
		}
		allEntities.add(id);
		for (String cmp : assemblage.keySet()) {
			setComponent(id, cmp, assemblage.get(cmp));
		}
		lastEntity = id;
		return id;
	}
	
	public static Long createEntity() {
		Long newEntity = getNextEntityId();
		allEntities.add(newEntity);
		lastEntity = newEntity;
		return newEntity;
	}
	
	public static Long getLastEntity() {
		return lastEntity;
	}
	
	public static void createEntity(Long id) {
		if (!doesEntityExist(id)) {
			allEntities.add(id);
			lastEntity = id;
		}
	}
	
	public static boolean doesEntityExist(Long entity) {
		return allEntities.contains(entity);
	}
	
	public static boolean doesDataExist(Long dataID) {
		return componentDataTable.containsKey(dataID);
	}
	
	public static List<ComponentPair> getAllComponents(Long entity) {
		if (!doesEntityExist(entity)) {
			throw new IllegalArgumentException("No such entity: " + entity);
		}
		Vector<ComponentPair> components = new Vector<>();
		for (Map.Entry<String, HashMap<Long, Long>> map : ecsMap.entrySet()) {
			if (map.getValue().containsKey(entity)) {
				String name = map.getKey();
				Long dataID = map.getValue().get(entity);
				String value = componentDataTable.get(dataID);
				if (value != null) {
					components.add(new ComponentPair(name, value));
				}
			}
		}
		return components;
	}
	
	public static boolean hasComponent(Long entity, String component) {
		if (getComponent(entity, component) != null) {
			return true;
		}
		return false;
	}
	
	public static boolean hasComponents(Long entity, String... components) {
		Boolean hasCmps = true;
		for (String cmp : components) {
			if (!hasComponent(entity, cmp)) {
				hasCmps = false;
				break;
			}
		}
		return hasCmps;
	}
	
	public static List<ComponentPair> getAllComponentsOfType(String type) {
		Vector<ComponentPair> components = new Vector<>();
		if (!ecsMap.containsKey(type)) {
			return components;
		}
		for (Long dataID : ecsMap.get(type).values()) {
			String value = componentDataTable.get(dataID);
			if (value != null) {
				components.add(new ComponentPair(type, value));
			}
		}
		return components;
	}
	
	public static List<Long> getAllEntities() {
		return allEntities;
	}
	
	public static List<Long> getAllEntitiesPossessingComponent(String type) {
		Vector<Long> entities = new Vector<>();
		if (ecsMap.containsKey(type)) {
			entities.addAll(ecsMap.get(type).keySet());
		}
		return entities;
	}
	
	public static List<Long> getAllEntitiesPossessingComponentValue(String type, String value) {
		Vector<Long> entities = new Vector<>();
		if (ecsMap.containsKey(type)) {
			for (Long entity : ecsMap.get(type).keySet()) {
				Long dataID = ecsMap.get(type).get(entity);
				if (componentDataTable.get(dataID).equals(value)) {
					entities.add(entity);
				}
			}
		}
		return entities;
	}
	
	public static ComponentPair getComponent(Long entity, String type) {
		if (!doesEntityExist(entity)) {
			return null;
		}
		if (!ecsMap.containsKey(type)) {
			return null;
		}
		Long dataID = ecsMap.get(type).get(entity);
		String value = componentDataTable.get(dataID);
		if (value != null) {
			return new ComponentPair(type, value);
		}
		return null;
	}
	
	public static String getComponentValue(Long entity, String type) {
		ComponentPair cmp = getComponent(entity, type);
		if (cmp == null) {
			return null;
		}
		return cmp.value();
	}
	
	private static Long getNextEntityId() {
		while (doesEntityExist(lastEntityId)) {
			lastEntityId++;
		}
		return lastEntityId;
	}
	
	private static Long getNextDataId() {
		while (doesDataExist(lastDataId)) {
			lastDataId++;
		}
		return lastDataId;
	}
	
	public static void killAll() {
		allEntities.removeAllElements();
		componentDataTable = new HashMap<Long, String>();
		for (String type : ecsMap.keySet()) {
			ecsMap.put(type, new HashMap<Long, Long>());
		}
		lastEntityId = 0L;
		lastEntity = null;
	}
	
	public static void killEntity(Long entity) {
		if (getComponent(entity, "Behaviour") != null) {
			GameLogger.get().addMessage("The " + getComponent(entity, "FormalName").value() + " has died!", Colors.libTCOD.red.normal);
		}
		allEntities.remove(entity);
		for (HashMap<Long, Long> map : ecsMap.values()) {
			if (map.get(entity) != null && map.get(entity) <= lastDataId) {
				lastDataId = map.get(entity);
			}
			componentDataTable.remove(map.get(entity));
			
			map.remove(entity);
		}
		if (getAllEntitiesPossessingComponentValue("Behaviour", "PlayerControlled").isEmpty()) {
			GameLogger.get().addMessage("You have died...", Colors.libTCOD.red.darker);
			GameLogger.get().addMessage("Press any key.", Colors.libTCOD.white.normal);
			GameLogger.get().update();
			Game.restart = true;
			Game.running = false;
			Game.waitForKey();
		}
		if (entity <= lastEntityId) {
			lastEntityId = entity;
		}
	}
	
	/**
	 * Matches entries of the form:
	 * TYPE \<NAME\> {
	 * ComponentType: 'DefaultValue'
	 * ComponentType2: 'DefaultValue2'
	 * ...
	 * }
	 * Capturing group 1 is the TYPE.  Group 2 is the NAME.  Group 3 is all the inner entries (everything between {}).
	 */
	private static Pattern declarationRegex = Pattern.compile(
			"((?:MOB)|(?:ITEM)|(?:TERRAIN)|(?:ENTITY)|(?:EVENT))\\s+\\<(.+?)\\>\\s+\\{\\s+((?:(?:[a-zA-Z0-9]+):\\s+'(?:.+?)'\\s+?)*)\\}");
	/**
	 * Matches inner entries of the form:
	 * ComponentType: 'DefaultValue'
	 * Capturing group 1 is the ComponentType.  Group 2 is the DefaultValue.
	 */
	private static Pattern componentRegex = Pattern.compile(
			"([a-zA-Z0-9]+):\\s+'([^']*)'\\s*");
	
	public static HashMap<String, Vector<ComponentPair>> parseData(List<String> lines) {
		HashMap<String, Vector<ComponentPair>> entries = new HashMap<>();
		String file = "";
		for (String line : lines) {
			file += " " + line;
		}
		Matcher matchDeclaration = declarationRegex.matcher(file);
		while (matchDeclaration.find()) {
			entries.put(matchDeclaration.group(2), new Vector<ComponentPair>());
			Matcher matchComponent = componentRegex.matcher(matchDeclaration.group(3));
			while (matchComponent.find()) {
				String cmpType = matchComponent.group(1).trim();
				String cmpValue = matchComponent.group(2).trim();
				ComponentPair newComponent = null;
				if (!(cmpType == null
						|| cmpType == "")) {
					newComponent = new ComponentPair(cmpType, cmpValue);
				}
				
				if (newComponent != null) {
					entries.get(matchDeclaration.group(2)).add(newComponent);
				}
			}
		}
		return entries;
	}

}
